﻿using System;
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace QuantumFractals
{
    public enum FractalObserver { Front, Back, Left, Right }
    public enum FractalPalette { Mono, Gray, Color, GrayLog, ColorLog,
        GrayHistoriogram, ColorHistoriogram, ColorLine, GrayLine }

    public struct Pixel
    {
        public int FrontDensity;
        public int BackDensity;
        public int LeftDensity;
        public int RightDensity;

        public void Clear()
        {
            FrontDensity = 0;
            BackDensity = 0;
            LeftDensity = 0;
            RightDensity = 0;
        }

        public int ForObserver(FractalObserver observer)
        {
            switch(observer)
            {
                case FractalObserver.Front:
                    return FrontDensity;
                case FractalObserver.Back:
                    return BackDensity;
                case FractalObserver.Left:
                    return LeftDensity;
                case FractalObserver.Right:
                    return RightDensity;
                default:
                    return FrontDensity;
            }
        }
    }

    public struct SumPixelDensitySummary
    {
        public int Front;
        public int Back;
        public int Left;
        public int Right;
        public int All;

        public int ForObserver(FractalObserver observer)
        {
            switch (observer)
            {
                case FractalObserver.Front:
                    return Front;
                case FractalObserver.Back:
                    return Back;
                case FractalObserver.Left:
                    return Left;
                case FractalObserver.Right:
                    return Right;
                default:
                    return Front;
            }
        }

        public void Clear()
        {
            Front = 0;
            Back = 0;
            Left = 0;
            Right = 0;
        }
    }

    public struct MaxPixelDensitySummary
    {
        public int Front;
        public int Back;
        public int Left;
        public int Right;

        public int ForObserver(FractalObserver observer)
        {
            switch (observer)
            {
                case FractalObserver.Front:
                    return Front;
                case FractalObserver.Back:
                    return Back;
                case FractalObserver.Left:
                    return Left;
                case FractalObserver.Right:
                    return Right;
                default:
                    return Front;
            }
        }

        public void Clear()
        {
            Front = 0;
            Back = 0;
            Left = 0;
            Right = 0;
        }
    }

    public class PixelHistoriogramSummary
    {
        public SortedDictionary<int, int> Front;
        public SortedDictionary<int, int> Back;
        public SortedDictionary<int, int> Left;
        public SortedDictionary<int, int> Right;

        public PixelHistoriogramSummary()
        {
            Front = new SortedDictionary<int, int>();
            Back = new SortedDictionary<int, int>();
            Left = new SortedDictionary<int, int>();
            Right = new SortedDictionary<int, int>();
        }

        public SortedDictionary<int, int> ForObserver(FractalObserver observer)
        {
            switch (observer)
            {
                case FractalObserver.Front:
                    return Front;
                case FractalObserver.Back:
                    return Back;
                case FractalObserver.Left:
                    return Left;
                case FractalObserver.Right:
                    return Right;
                default:
                    return Front;
            }
        }

        public void Clear()
        {
            Front.Clear();
            Back.Clear();
            Left.Clear();
            Right.Clear();
        }
    }

    public class QRenderer
    {
        private int pixelResolution = 600;
        private int ballResolution = 100;
        private int[, ,] balls;
        private int max3dDensity = 0;
        private Pixel[,] pixels;
        private MaxPixelDensitySummary maxPixelDensity = new MaxPixelDensitySummary();
        private SumPixelDensitySummary sumPixelDensity = new SumPixelDensitySummary();
        private PixelHistoriogramSummary pixelHistoriogram = new PixelHistoriogramSummary();

        public int PixelResolution { get { return pixelResolution; } }
        public int Resolution3d { get { return ballResolution; } }
        public PixelHistoriogramSummary Historiogram { get { return pixelHistoriogram; } }
        public MaxPixelDensitySummary MaxPixelDensity { get { return maxPixelDensity; } }
        public SumPixelDensitySummary SumPixelDensity { get { return sumPixelDensity; } }
        public Pixel[,] Pixels { get { return pixels; } }
        public int[,,] Balls { get { return balls; } } 

        public QRenderer(int newPixelResolution, int newBallResolution)
        {
            pixelResolution = newPixelResolution;
            ballResolution = newBallResolution;
            pixels = new Pixel[pixelResolution, pixelResolution];
            balls = new int[ballResolution, ballResolution, ballResolution];
        }

        private void AddPoint2d(QPoint point)
        {
            //Skalowanie? int x = resolution / 2 + (int)(resolution / 2 * scale * (point.X - centerX));
            int x = pixelResolution / 2 + (int)(pixelResolution / 2 * point.X);
            int y = pixelResolution / 2 + (int)(pixelResolution / 2 * point.Y);
            int z = pixelResolution / 2 + (int)(pixelResolution / 2 * point.Z);

            if (x < pixelResolution && y < pixelResolution && z < pixelResolution)
            {
                sumPixelDensity.All++;
                if (point.Z >= 0)
                {
                    pixels[x, y].FrontDensity++;
                    maxPixelDensity.Front = Math.Max(pixels[x, y].FrontDensity, maxPixelDensity.Front);
                    sumPixelDensity.Front++;
                    if (pixelHistoriogram.Front.ContainsKey(pixels[x, y].FrontDensity))
                        pixelHistoriogram.Front[pixels[x, y].FrontDensity]++;
                    else
                        pixelHistoriogram.Front[pixels[x, y].FrontDensity] = 1;
                }
                else
                {
                    pixels[x, y].BackDensity++;
                    maxPixelDensity.Back = Math.Max(pixels[x, y].BackDensity, maxPixelDensity.Back);
                    sumPixelDensity.Back++;
                    if (pixelHistoriogram.Back.ContainsKey(pixels[x, y].BackDensity))
                        pixelHistoriogram.Back[pixels[x, y].BackDensity]++;
                    else
                        pixelHistoriogram.Back[pixels[x, y].BackDensity] = 1;
                }

                if (point.X >= 0)
                {
                    pixels[z, y].RightDensity++;
                    maxPixelDensity.Right = Math.Max(pixels[z, y].RightDensity, maxPixelDensity.Right);
                    sumPixelDensity.Right++;
                    if (pixelHistoriogram.Right.ContainsKey(pixels[z, y].RightDensity))
                        pixelHistoriogram.Right[pixels[z, y].RightDensity]++;
                    else
                        pixelHistoriogram.Right[pixels[z, y].RightDensity] = 1;
                }
                else
                {
                    pixels[z, y].LeftDensity++;
                    maxPixelDensity.Left = Math.Max(pixels[z, y].LeftDensity, maxPixelDensity.Left);
                    sumPixelDensity.Left++;
                    if (pixelHistoriogram.Left.ContainsKey(pixels[z, y].LeftDensity))
                        pixelHistoriogram.Left[pixels[z, y].LeftDensity]++;
                    else
                        pixelHistoriogram.Left[pixels[z, y].LeftDensity] = 1;
                }
            }
        }

        public void AddPoint3d(QPoint point)
        {
            int x = ballResolution / 2 + (int)(point.X * (ballResolution / 2 - 1));
            int y = ballResolution / 2 + (int)(point.Y * (ballResolution / 2 - 1));
            int z = ballResolution / 2 + (int)(point.Z * (ballResolution / 2 - 1));
            balls[x, y, z]++;
            max3dDensity = Math.Max(balls[x, y, z], max3dDensity);
        }

        public void AddPoint(QPoint point)
        {
            AddPoint3d(point);
            AddPoint2d(point);
        }

        public void Clear()
        {
            pixelHistoriogram.Clear();
            maxPixelDensity.Clear();
            sumPixelDensity.Clear();
            for (int x = 0; x < pixelResolution; x++)
                for (int y = 0; y < pixelResolution; y++)
                    pixels[x, y].Clear();

        }

        public void SaveToCsv(string fileName, char separator)
        {
            using (StreamWriter sw = new StreamWriter(fileName))
            {
                sw.WriteLine(
                    "FrontX" + separator + "FrontY" + separator + 
                    "FrontDensity" + separator + 
                    "BackDensity" + separator + 
                    "LeftDensity" + separator + 
                    "RightDensity" + separator);
                for (int x = 0; x < pixelResolution; x++)
                    for (int y = 0; y < pixelResolution; y++)
                        sw.WriteLine(
                            x.ToString() + separator + y.ToString() + separator +
                            pixels[x, y].FrontDensity.ToString() + separator +
                            pixels[x, y].BackDensity.ToString() + separator +
                            pixels[x, y].LeftDensity.ToString() + separator +
                            pixels[x, y].RightDensity.ToString() + separator);
            }
        }

        public void SaveAsPovray(string fileName)
        {
            string headerFormat =
                "// File autogenerated by QuantumFractals program\r\n" +
                "// (C) 2008 Arkadiusz Jadczyk, Krzysztof Arkadiusz Prusik\r\n" +
                "// You can edit this file to get better effect\r\n" +
                "// Fractal position <0,0,0>x<{0},{0},{0}>\r\n" +
                "\r\n" +
                "#declare MaxDensity = {2};\r\n" +
                "camera{{ location <{1}, {1}, -{1} * 1.3> look_at <{1}, {1}, {1} * 1.3>}}\r\n" +
                "plane{{ z, {1} * 4 hollow on texture {{\r\n" +
                "   pigment {{ color red 255 green 255 blue 255 }} }} }}\r\n" +
                "\r\n" +
                "#macro DrawBall(X, Y, Z, Density)\r\n" +
                "   #local GrayColor = 255 * Density / MaxDensity;\r\n" +
                "   sphere {{ <X, Y, Z>, 1\r\n" +
                "      pigment {{ color red GrayColor green GrayColor blue GrayColor }} }}\r\n" +
                "#end\r\n" +
                "\r\n";
            
            string ballFormat = "DrawBall({0}, {1}, {2}, {3})";

            using (StreamWriter sw = new StreamWriter(fileName))
            {
                sw.WriteLine(String.Format(headerFormat, ballResolution, ballResolution / 2, 
                    max3dDensity));
                for (int x = 0; x < ballResolution; x++)
                    for (int y = 0; y < ballResolution; y++)
                        for (int z = 0; z < ballResolution; z++)
                            if (balls[x, y, z] > 0)
                                sw.WriteLine(String.Format(ballFormat, x, y, z, balls[x, y, z]));
            }
        }

        public MaxPixelDensitySummary CalcSummary()
        {
            MaxPixelDensitySummary summary = new MaxPixelDensitySummary();
            for (int x = 0; x < pixelResolution; x++)
                for (int y = 0; y < pixelResolution; y++)
                {
                    summary.Front = Math.Max(pixels[x, y].FrontDensity, summary.Front);
                    summary.Back = Math.Max(pixels[x, y].BackDensity, summary.Back);
                    summary.Left = Math.Max(pixels[x, y].LeftDensity, summary.Left);
                    summary.Right = Math.Max(pixels[x, y].RightDensity, summary.Right);
                }
            return summary;
        }

        public Bitmap DrawFractal(FractalObserver observer, FractalPalette palette)
        {
            int observerMaxDensity = maxPixelDensity.ForObserver(observer);
            SortedDictionary<int, int> observerHistoriogram = pixelHistoriogram.ForObserver(observer);
            Bitmap bmp = new Bitmap(pixelResolution, pixelResolution);

            for (int x = 0; x < pixelResolution; x++)
                for (int y = 0; y < pixelResolution; y++)
                    bmp.SetPixel(x, y, GetPaletteColor(pixels[x, y].ForObserver(observer),
                        observerMaxDensity, palette, observerHistoriogram));

            for (int x = pixelResolution; x < bmp.Width; x++)
                for (int y = pixelResolution; y < bmp.Height; y++)
                    bmp.SetPixel(x, y, GetPaletteColor(pixels[0, 0].ForObserver(observer),
                        observerMaxDensity, palette, observerHistoriogram));
            return bmp;
        }

        public Bitmap DrawMoonFractal(FractalObserver observer, FractalPalette palette)
        {
            int observerMaxDensity = maxPixelDensity.ForObserver(observer);
            SortedDictionary<int, int> observerHistoriogram = pixelHistoriogram.ForObserver(observer);
            int maxHigh = pixelResolution / 5;
            Bitmap bmp = new Bitmap(pixelResolution + pixelResolution, pixelResolution + maxHigh);

            for(int y = pixelResolution - 1; y > 0; y--)
                for (int x = 0; x < pixelResolution; x++)
                {
                    int densityScaled = pixels[x, y].ForObserver(observer) * maxHigh / 
                        observerMaxDensity;
                    for (int h = 0; h < densityScaled; h++)
                        bmp.SetPixel(x + y, pixelResolution + maxHigh - 1 - y - h,
                            GetPaletteColor(pixels[x, y].ForObserver(observer),
                                observerMaxDensity, palette, observerHistoriogram));
                            //Color.FromArgb(255 * h / densityScaled, 255, 0));
                }

            return bmp;
        }

        private Color GetPaletteColor(int density, int maxDensity, 
            FractalPalette palette, SortedDictionary<int, int> historiogram)
        {
            double percent = (double)density / (double)maxDensity;
            int gray = 0;
            int red = 0;
            int green = 0;
            int blue = 0;
            switch (palette)
            {
                case FractalPalette.Mono:
                    if (density > 0)
                        return Color.Black;
                    else
                        return Color.White;
                case FractalPalette.Gray:
                    if (density == 0)
                        return Color.White;
                    else
                    {
                        gray = 255 - (int)(percent * 255);
                        return Color.FromArgb(gray, gray, gray);
                    }
                case FractalPalette.Color:
                    if (density == 0)
                        return Color.White;
                    else
                    {
                        gray = (int)(percent * 0xFFFFFF);
                        green = (gray / 0x10000) % 0x100;
                        blue = (gray / 0x100) % 0x100;
                        red = gray % 0x100;
                        return Color.FromArgb(red, green, blue);
                    }
                case FractalPalette.GrayLog:
                    gray = (int)Math.Log((double)density, 1.000001);
                    gray = gray * 255 / Math.Max((int)Math.Log((double)maxDensity, 1.000001), 1);
                    return Color.FromArgb(255 - gray % 255, 255 - gray % 255, 255 - gray % 255);
                case FractalPalette.ColorLog:
                    gray = (int)Math.Log((double)density, 1.000001);
                    gray = gray * 16777215 / Math.Max((int)Math.Log((double)maxDensity, 1.000001), 1);
                    return Color.FromArgb(16777215 - gray % 16777215);
                case FractalPalette.GrayHistoriogram:
                    if (density == 0)
                        return Color.White;
                    else
                    {
                        gray = 255 - Math.Min(historiogram[density], 255);
                        return Color.FromArgb(gray, gray, gray);
                    }
                case FractalPalette.ColorHistoriogram:
                    if (density == 0)
                        return Color.White;
                    else
                    {
                        gray = Math.Min(historiogram[density], 0xFFFFFF);
                        green = (gray / 0x10000) % 0x100;
                        blue = (gray / 0x100) % 0x100;
                        red = gray % 0x100;
                        return Color.FromArgb(red, green, blue);
                    }
                case FractalPalette.GrayLine:
                    if (density == 0)
                        return Color.White;
                    else
                    {
                        gray = 255 - (int)((historiogram[1] - historiogram[1] * percent) * 255 / 
                            historiogram[1]);
                        return Color.FromArgb(gray, gray, gray);
                    }
                case FractalPalette.ColorLine:
                    if (density == 0)
                        return Color.White;
                    else
                    {
                        gray = (int)((historiogram[1] - historiogram[1] * percent) * 0xFFFFFF / 
                            historiogram[1]);
                        red = (gray / 0x10000) % 0x100;
                        blue = (gray / 0x100) % 0x100;
                        green = gray % 0x100;
                        return Color.FromArgb(red, green, blue);
                    }
                    
                default:
                    throw new Exception("Bad color palette");
            }
        }
    }
}